From 43d330d64a81d99f4ad7f5e92626c9922654402c Mon Sep 17 00:00:00 2001 From: Timo Sirainen Date: Thu, 26 Feb 2026 12:29:12 +0200 Subject: [PATCH] [PATCH] lib: Preserve errno in our malloc() and free() wrappers Various places assume that e.g. t_strdup_printf() calls and such don't modify errno. But because they internally call malloc() or calloc(), this isn't actually guaranteed now and it can happen at least with newer glibc versions. Explicitly preserve the errno for these calls where it might be a problem. Gbp-Pq: Name lib_Preserve_errno_in_our_malloc_and_free_wrappers.patch --- src/lib/data-stack.c | 20 +++++++------------- src/lib/mempool-allocfree.c | 11 +++++++++++ src/lib/mempool-alloconly.c | 5 +++++ src/lib/mempool-system.c | 20 +++++++------------- 4 files changed, 30 insertions(+), 26 deletions(-) diff --git a/src/lib/data-stack.c b/src/lib/data-stack.c index 477ad91..fd29d13 100644 --- a/src/lib/data-stack.c +++ b/src/lib/data-stack.c @@ -214,6 +214,7 @@ static void block_canary_check(struct stack_block *block) static void free_blocks(struct stack_block *block) { struct stack_block *next; + int old_errno = errno; /* free all the blocks, except if any of them is bigger than unused_block, replace it */ @@ -237,6 +238,7 @@ static void free_blocks(struct stack_block *block) block = next; } + errno = old_errno; } #ifdef DEBUG @@ -367,6 +369,7 @@ static struct stack_block *mem_block_alloc(size_t min_size) { struct stack_block *block; size_t prev_size, alloc_size; + int old_errno = errno; prev_size = current_block == NULL ? 0 : current_block->size; /* Use INITIAL_STACK_SIZE without growing it to nearest power. */ @@ -386,6 +389,7 @@ static struct stack_block *mem_block_alloc(size_t min_size) i_panic("data stack: Out of memory when allocating %zu bytes", alloc_size + SIZEOF_MEMBLOCK); } + errno = old_errno; block->size = alloc_size; block->canary = BLOCK_CANARY; mem_block_reset(block); @@ -457,9 +461,7 @@ static void *t_malloc_real(size_t size, bool permanent) void *ret; size_t alloc_size; bool warn = FALSE; -#ifdef DEBUG int old_errno = errno; -#endif if (unlikely(size == 0 || size > SSIZE_T_MAX)) i_panic("Trying to allocate %zu bytes", size); @@ -519,17 +521,9 @@ static void *t_malloc_real(size_t size, bool permanent) current_block->left -= alloc_size; if (warn) T_BEGIN { - /* sending event can cause errno changes. */ -#ifdef DEBUG - i_assert(errno == old_errno); -#else - int old_errno = errno; -#endif /* warn after allocation, so if e_debug() wants to allocate more memory we don't go to infinite loop */ data_stack_send_grow_event(alloc_size); - /* reset errno back to what it was */ - errno = old_errno; } T_END; #ifdef DEBUG memcpy(ret, &size, sizeof(size)); @@ -538,10 +532,8 @@ static void *t_malloc_real(size_t size, bool permanent) had used t_buffer_get(). */ memset(PTR_OFFSET(ret, size), CLEAR_CHR, MEM_ALIGN(size + SENTRY_COUNT) - size); - - /* we rely on errno not changing. it shouldn't. */ - i_assert(errno == old_errno); #endif + errno = old_errno; return ret; } @@ -739,8 +731,10 @@ size_t data_stack_get_used_size(void) void data_stack_free_unused(void) { + int old_errno = errno; free(unused_block); unused_block = NULL; + errno = old_errno; } void data_stack_init(void) diff --git a/src/lib/mempool-allocfree.c b/src/lib/mempool-allocfree.c index 239db03..b2f6f27 100644 --- a/src/lib/mempool-allocfree.c +++ b/src/lib/mempool-allocfree.c @@ -146,6 +146,7 @@ static const struct pool static_allocfree_pool = { pool_t pool_allocfree_create(const char *name ATTR_UNUSED) { struct allocfree_pool *pool; + int old_errno = errno; (void) COMPILE_ERROR_IF_TRUE(SIZEOF_POOLBLOCK > (SSIZE_T_MAX - POOL_MAX_ALLOC_SIZE)); @@ -154,6 +155,7 @@ pool_t pool_allocfree_create(const char *name ATTR_UNUSED) if (pool == NULL) i_fatal_status(FATAL_OUTOFMEM, "calloc(1, %zu): Out of memory", SIZEOF_ALLOCFREE_POOL); + errno = old_errno; #ifdef DEBUG pool->name = strdup(name); #endif @@ -175,6 +177,8 @@ pool_t pool_allocfree_create_clean(const char *name) static void pool_allocfree_destroy(struct allocfree_pool *apool) { + int old_errno = errno; + pool_allocfree_clear(&apool->pool); if (apool->clean_frees) safe_memset(apool, 0, SIZEOF_ALLOCFREE_POOL); @@ -182,6 +186,7 @@ static void pool_allocfree_destroy(struct allocfree_pool *apool) free(apool->name); #endif free(apool); + errno = old_errno; } static const char *pool_allocfree_get_name(pool_t pool ATTR_UNUSED) @@ -256,11 +261,13 @@ static void *pool_allocfree_malloc(pool_t pool, size_t size) { struct allocfree_pool *apool = container_of(pool, struct allocfree_pool, pool); + int old_errno = errno; struct pool_block *block = calloc(1, SIZEOF_POOLBLOCK + size); if (block == NULL) i_fatal_status(FATAL_OUTOFMEM, "calloc(1, %zu): Out of memory", SIZEOF_POOLBLOCK + size); + errno = old_errno; block->size = size; return pool_block_attach(apool, block); } @@ -269,10 +276,12 @@ static void pool_allocfree_free(pool_t pool, void *mem) { struct allocfree_pool *apool = container_of(pool, struct allocfree_pool, pool); + int old_errno = errno; struct pool_block *block = pool_block_detach(apool, mem); if (apool->clean_frees) safe_memset(block, 0, SIZEOF_POOLBLOCK+block->size); free(block); + errno = old_errno; } static void *pool_allocfree_realloc(pool_t pool, void *mem, @@ -281,6 +290,7 @@ static void *pool_allocfree_realloc(pool_t pool, void *mem, struct allocfree_pool *apool = container_of(pool, struct allocfree_pool, pool); unsigned char *new_mem; + int old_errno = errno; struct pool_block *block = pool_block_detach(apool, mem); if (old_size == SIZE_MAX) @@ -288,6 +298,7 @@ static void *pool_allocfree_realloc(pool_t pool, void *mem, if ((new_mem = realloc(block, SIZEOF_POOLBLOCK+new_size)) == NULL) i_fatal_status(FATAL_OUTOFMEM, "realloc(block, %zu)", SIZEOF_POOLBLOCK+new_size); + errno = old_errno; /* zero out new memory */ if (new_size > old_size) diff --git a/src/lib/mempool-alloconly.c b/src/lib/mempool-alloconly.c index 79823dc..e1b0925 100644 --- a/src/lib/mempool-alloconly.c +++ b/src/lib/mempool-alloconly.c @@ -281,6 +281,8 @@ pool_t pool_alloconly_create_clean(const char *name, size_t size) static void pool_alloconly_free_block(struct alloconly_pool *apool ATTR_UNUSED, struct pool_block *block) { + int old_errno = errno; + #ifdef DEBUG safe_memset(block, CLEAR_CHR, SIZEOF_POOLBLOCK + block->size); #else @@ -290,6 +292,7 @@ static void pool_alloconly_free_block(struct alloconly_pool *apool ATTR_UNUSED, } #endif free(block); + errno = old_errno; } static void @@ -381,11 +384,13 @@ static void block_alloc(struct alloconly_pool *apool, size_t size) #endif } + int old_errno = errno; block = calloc(size, 1); if (unlikely(block == NULL)) { i_fatal_status(FATAL_OUTOFMEM, "block_alloc(%zu" "): Out of memory", size); } + errno = old_errno; block->prev = apool->block; apool->block = block; diff --git a/src/lib/mempool-system.c b/src/lib/mempool-system.c index 7d1addc..e4c0134 100644 --- a/src/lib/mempool-system.c +++ b/src/lib/mempool-system.c @@ -98,35 +98,27 @@ static void pool_system_unref(pool_t *pool ATTR_UNUSED) static void *pool_system_malloc(pool_t pool ATTR_UNUSED, size_t size) { void *mem; -#ifdef DEBUG int old_errno = errno; -#endif mem = calloc(size, 1); if (unlikely(mem == NULL)) { i_fatal_status(FATAL_OUTOFMEM, "pool_system_malloc(%zu): " "Out of memory", size); } -#ifdef DEBUG - /* we rely on errno not changing. it shouldn't. */ - i_assert(errno == old_errno); -#endif + errno = old_errno; return mem; } void pool_system_free(pool_t pool ATTR_UNUSED, void *mem ATTR_UNUSED) { -#ifdef DEBUG int old_errno = errno; -#endif + #if defined(HAVE_MALLOC_USABLE_SIZE) && defined(DEBUG) - safe_memset(mem, CLEAR_CHR, malloc_usable_size(mem)); + if (mem != NULL) + safe_memset(mem, CLEAR_CHR, malloc_usable_size(mem)); #endif free(mem); -#ifdef DEBUG - /* we rely on errno not changing. it shouldn't. */ - i_assert(errno == old_errno); -#endif + errno = old_errno; } static void *pool_system_realloc(pool_t pool ATTR_UNUSED, void *mem, @@ -137,11 +129,13 @@ static void *pool_system_realloc(pool_t pool ATTR_UNUSED, void *mem, old_size <= malloc_usable_size(mem)); #endif + int old_errno = errno; mem = realloc(mem, new_size); if (unlikely(mem == NULL)) { i_fatal_status(FATAL_OUTOFMEM, "pool_system_realloc(%zu): " "Out of memory", new_size); } + errno = old_errno; if (old_size < new_size) { /* clear new data */ -- 2.30.2